package com.ruoyi.pac4j;

import cc.renzhihao.sso.pac4j.IAMCallbackClientFinder;
import cn.hutool.core.net.url.UrlBuilder;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.auth.domain.SysAuth;
import com.ruoyi.project.system.auth.service.AuthService;
import org.pac4j.core.client.BaseClient;
import org.pac4j.core.client.Client;
import org.pac4j.core.client.Clients;
import org.pac4j.core.client.IndirectClient;
import org.pac4j.core.client.finder.ClientFinder;
import org.pac4j.core.config.Config;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.context.session.SessionStore;
import org.pac4j.core.credentials.Credentials;
import org.pac4j.core.engine.DefaultCallbackLogic;
import org.pac4j.core.exception.TechnicalException;
import org.pac4j.core.exception.http.HttpAction;
import org.pac4j.core.exception.http.RedirectionAction;
import org.pac4j.core.http.adapter.HttpActionAdapter;
import org.pac4j.core.profile.UserProfile;
import org.pac4j.oidc.credentials.OidcCredentials;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;

import static org.pac4j.core.util.CommonHelper.assertNotNull;
import static org.pac4j.core.util.CommonHelper.assertTrue;

public class MyCallbackLogic extends DefaultCallbackLogic {

    private static final Logger log = LoggerFactory.getLogger(Pac4jConfigInitializingBean.class);

    private final AuthService authService;
    private final ClientFinder clientFinder = new IAMCallbackClientFinder();

    public MyCallbackLogic(AuthService authService) {
        this.authService = authService;
    }

    @Override
    public Object perform(final WebContext webContext, final SessionStore sessionStore, final Config config,
                          final HttpActionAdapter httpActionAdapter, final String inputDefaultUrl, final Boolean inputRenewSession,
                          final String defaultClient) {
        log.debug("=== CALLBACK ===");

        HttpAction action;
        try {

            final boolean renewSession = inputRenewSession == null || inputRenewSession;

            // checks
            assertNotNull("clientFinder", clientFinder);
            assertNotNull("webContext", webContext);
            assertNotNull("config", config);
            assertNotNull("httpActionAdapter", httpActionAdapter);
            final Clients clients = config.getClients();
            assertNotNull("clients", clients);

            // logic
            final List<Client> foundClients = clientFinder.find(clients, webContext, defaultClient);
            assertTrue(foundClients != null && foundClients.size() == 1,
                    "unable to find one indirect client for the callback: check the callback URL for a client name parameter or suffix path"
                            + " or ensure that your configuration defaults to one indirect client");

            final Client foundClient = foundClients.get(0);
            log.debug("foundClient: {}", foundClient);
            assertNotNull("foundClient", foundClient);

            String defaultUrl = "";
            SysAuth sysAuth = authService.getAuthWayByClientName(foundClient.getName());
            if (sysAuth != null) {
                defaultUrl = UrlBuilder.ofHttp(sysAuth.getLocalServer()).addPath(sysAuth.getSuccessRedirectUrl()).toString();
                if (defaultUrl.endsWith("/")) {
                    defaultUrl = defaultUrl.substring(0, defaultUrl.length() - 1);
                }
            }

            if (StringUtils.isEmpty(defaultUrl)) {
                throw new TechnicalException("Client is not support");
            }

            Optional<UserProfile> optProfile;
            try {
                Credentials credentials = foundClient.getCredentials(webContext, sessionStore).orElse(null);
                log.debug("credentials: {}", credentials);

                if (credentials instanceof OidcCredentials) {
                    final OidcCredentials oidcCredentials = (OidcCredentials) credentials;
                    if (oidcCredentials.getCode() == null && oidcCredentials.getIdToken() == null
                            && oidcCredentials.getAccessToken() == null && oidcCredentials.getRefreshToken() == null) {
                        credentials = null;
                    }
                }

                optProfile = foundClient.getUserProfile(credentials, webContext, sessionStore);
            } catch (TechnicalException e) {
                optProfile = Optional.empty();
            }
            log.debug("optProfile: {}", optProfile);
            if (optProfile.isPresent()) {
                final UserProfile profile = optProfile.get();
                final boolean saveProfileInSession = ((BaseClient) foundClient).getSaveProfileInSession(webContext, profile);
                final boolean multiProfile = ((BaseClient) foundClient).isMultiProfile(webContext, profile);
                log.debug("saveProfileInSession: {} / multiProfile: {}", saveProfileInSession, multiProfile);
                saveUserProfile(webContext, sessionStore, config, profile, saveProfileInSession, multiProfile, renewSession);
            } else {
                IndirectClient client = (IndirectClient) foundClient;
                final Optional<RedirectionAction> redirectionAction = client.getRedirectionActionBuilder().getRedirectionAction(webContext, sessionStore);
                if (redirectionAction.isPresent()) {
                    return httpActionAdapter.adapt(redirectionAction.get(), webContext);
                }
            }

            action = redirectToOriginallyRequestedUrl(webContext, sessionStore, defaultUrl);
        } catch (final RuntimeException e) {
            return handleException(e, httpActionAdapter, webContext);
        }

        return httpActionAdapter.adapt(action, webContext);
    }
}
